package com.wxapp.gateway.web.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.wxapp.common.base.util.encryption.EncryptionUtil;
import com.wxapp.common.base.util.jackson.JacksonUtil;
import com.wxapp.common.base.util.md5.Md5Util;
import com.wxapp.common.base.util.redis.RedisUtil;
import com.wxapp.common.constant.constant.LoginConstant;
import com.wxapp.common.constant.constant.PatternConstant;
import com.wxapp.common.constant.enums.ResultEnum;
import com.wxapp.common.constant.pojo.vm.LoginInfoVm;
import com.wxapp.common.constant.pojo.vo.UserVo;
import com.wxapp.common.constant.properties.LoginProperties;
import com.wxapp.common.constant.properties.WhiteRoleProperties;
import com.wxapp.common.constant.properties.WhiteUrlProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

/**
 * @author lgl
 */
@Component
public class CheckUtil {
    private final RedisUtil redisUtil;
    private final LoginProperties loginProperties;
    private final WhiteRoleProperties whiteRoleProperties;
    private final WhiteUrlProperties whiteUrlProperties;

    public CheckUtil(RedisUtil redisUtil, LoginProperties loginProperties, WhiteRoleProperties whiteRoleProperties, WhiteUrlProperties whiteUrlProperties) {
        this.redisUtil = redisUtil;
        this.loginProperties = loginProperties;
        this.whiteRoleProperties = whiteRoleProperties;
        this.whiteUrlProperties = whiteUrlProperties;
    }
    /**
     * 获取ip
     */
    private static String getIp(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        String key = "unknown";
        if (ip == null || ip.isEmpty() || key.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || key.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || key.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || key.equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || key.equalsIgnoreCase(ip)) {
            ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
        }
        if (ip != null && !ip.isEmpty() && ip.indexOf(",") > 0) {
            ip = ip.substring(0, ip.indexOf(","));
        }
        assert ip != null;
        return ip.trim();
    }

    /**
     * 判断是否yousql 注入
     */
    private boolean isSql(MultiValueMap<String, String> map) {
        return map.entrySet().stream().parallel().anyMatch(entry -> {
            //这里需要将参数转换为小写来处理
            String lowerValue = Optional.ofNullable(entry.getValue()).map(Object::toString).map(String::toLowerCase).orElse("");
            return Pattern.matches(PatternConstant.SQL_KEYWORDS, lowerValue);
        });
    }

    /**
     * ip限制
     */
    private boolean ipLimit(ServerHttpRequest request, long second, Long limitCount) {
        var ip = getIp(request);
        if (ip.isEmpty()) {
            return false;
        }
        Long ipCount = redisUtil.incr(LoginConstant.LOGIN_LIMIT + ip + ":" + request.getURI().getPath(), 1, second);
        if (ipCount == null) {
            return false;
        }
        return ipCount >= limitCount;
    }
    /**
     * 校验白名单
     */
    private boolean isWhitUrl(String url) {
        return whiteUrlProperties.getList().stream().map(s -> s.replace("/**", "")).anyMatch(s -> url.startsWith(s) || url.contains(s) || url.endsWith(s));
    }
    /**
     * 判断是否有权限
     */
    public ResultEnum havePermission(ServerHttpRequest request, String token) {
        if (isSql(request.getQueryParams())) {
            return ResultEnum.ILLEGAL_REQUEST;
        }
        if (ipLimit(request, 5L, 10L)) {
            return ResultEnum.FREQUENT_REQUESTS;
        }
        if (isWhitUrl(request.getURI().getPath())){
            return ResultEnum.SUCCESS;
        }
        if (token ==null || "".equals(token)){
            return ResultEnum.UNAUTHORIZED;
        }
        DecodedJWT verify = null;
        try {
            verify = JWT.require(Algorithm.HMAC256(loginProperties.getSecret())).build().verify(token);
            if (verify.getSubject() != null && !"".equals(verify.getSubject()) && (verify.getExpiresAt().getTime() - System.currentTimeMillis()) < (loginProperties.getExpireTime())) {
                return ResultEnum.REQUEST_TIMEOUT;
            }
        } catch (Exception e) {
            return ResultEnum.REQUEST_TIMEOUT;
        }
        String  userInfo = EncryptionUtil.aesDecrypt(Md5Util.md5(getIp(request)), verify.getSubject());
        if (userInfo == null || "".equals(userInfo)) {
            return ResultEnum.REQUEST_TIMEOUT;
        }
        String tokenKey = LoginConstant.PERMISSION_LIST + JacksonUtil.parseObject(userInfo, UserVo.class).getId();
        //判断是否有权限
        var per = (LoginInfoVm) redisUtil.get(tokenKey);
        if (per == null || !per.getToken().equals(token)) {
            return ResultEnum.REQUEST_TIMEOUT;
        }
        if (per.getPermissions() == null || per.getPermissions().isEmpty()) {
            return ResultEnum.UNAUTHORIZED;
        }
        if (per.getPermissions().stream().noneMatch(s -> whiteRoleProperties.getList().contains(s.getRoleCode()))) {
            //判断是否有权限
            String permission = request.getHeaders().getFirst(LoginConstant.PERMISSION);
            if (per.getPermissions().stream().noneMatch(s -> s.getPermission().equals(permission))) {//没有权限
                return ResultEnum.UNAUTHORIZED;
            }
        }
        //自动续期
        var expire = redisUtil.getExpire(tokenKey);
        if (expire <= loginProperties.getRefreshExpireTime()) {
            redisUtil.expire(tokenKey, loginProperties.getExpireTime());
        }
        return ResultEnum.SUCCESS;
    }
}
